package com.universest.swordholder.task

import com.universest.swordholder.task.Task.TaskScope
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.withContext
import java.util.concurrent.CopyOnWriteArrayList

class TaskCancelException(message: String, cause: Throwable? = null): Exception(message)

abstract class AbstractTask<A,B>(val name: String = "") {

    abstract val progress: Flow<Pair<Int, Int>>

    abstract val message:Flow<String>

    private val _end = MutableStateFlow(false)

    protected var coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO

    private val finallyDoList: MutableList<suspend (Result<B>) -> Unit> = CopyOnWriteArrayList()

    protected val cancelled = MutableStateFlow(false)

    val end: Flow<Boolean>
         = flow {
             emit(false)
             _end
                 .takeWhile { !it }
                 .collect { emit(it) }
             emit(true)
         }

    suspend fun execute(param:A): Result<B>{
        return withContext(coroutineDispatcher) {
            _end.emit(false)
            val result = try {
                checkCancelled()
                val callResult = call(param)
                checkCancelled()
                Result.success(callResult)
            } catch (e: Exception) {
                Result.failure(e)
            }
            finallyDoList.forEach { it(result) }
            _end.emit(true)
            result
        }
    }

    protected fun checkCancelled() {
        if (cancelled.value) throw TaskCancelException("Task cancelled")
    }

    open suspend fun cancel(){
        cancelled.emit(true)
        if(this is TaskGroup){
            this.tasks.filter { it !=  this && !it.cancelled.value }.forEach { it.cancel() }
        }
    }

    fun <C> convert(converter: (B) -> C): AbstractTask<A,C>{
        return ConvertTask(this,name,converter)
    }

    fun <C> combineTask(name: String = "", task: AbstractTask<B,C>):CombinedTask<A,B,C>{
        return CombinedTask<A,B,C>(if(name.isEmpty())this.name else name,this,task)
    }

    fun <C> combineTask(name: String = "", taskFunc: suspend TaskScope<B,C>.(B) -> C):CombinedTask<A,B,C>{
        return CombinedTask<A,B,C>(if(name.isEmpty())this.name else name,this,Task(taskFunc = taskFunc))
    }

    fun finallyDo(finallyDo: suspend (Result<B>) -> Unit): AbstractTask<A,B>{
        finallyDoList.add(finallyDo)
        return this
    }

    fun withContext(coroutineDispatcher: CoroutineDispatcher): AbstractTask<A,B>{
        this.coroutineDispatcher = coroutineDispatcher
        return this
    }

    fun isEnd(): Boolean{
        return _end.value
    }

    protected abstract suspend fun call(param:A):B
}

open class Task<A,B>(name: String = "", val progressMaxValue: Int = 1, initialMessage: String = "", val taskFunc: suspend TaskScope<A,B>.(A) -> B): AbstractTask<A,B>(name){

    interface TaskScope<A,B>{
        suspend fun updateProgress(current: Int, max: Int)
        suspend fun updateMessage(message: String)
        suspend fun isCancelled(): Boolean
        suspend fun checkCancelled(): Unit
    }

    protected val mutableProgressFlow = MutableStateFlow(Pair(0,progressMaxValue))

    protected val mutableMessageFlow = MutableStateFlow(initialMessage)

    private val taskScope = object : TaskScope<A,B>{
        override suspend fun updateProgress(current: Int, max: Int) {
            mutableProgressFlow.emit(current to max)
        }

        override suspend fun updateMessage(message: String) {
            mutableMessageFlow.emit(message)
        }

        override suspend fun isCancelled(): Boolean {
            return cancelled.value
        }

        override suspend fun checkCancelled() {
            if (cancelled.value) throw TaskCancelException("Task cancelled")
        }
    }
    override suspend fun call(param: A): B {
        mutableProgressFlow.emit(0 to progressMaxValue)
        val result = taskScope.taskFunc(param)
        mutableProgressFlow.emit(progressMaxValue to progressMaxValue)
        return result
    }

    override val progress: Flow<Pair<Int, Int>>
        = flow {
            combine(mutableProgressFlow,end){
                progress,done ->
                Pair(progress,done)
            }.takeWhile { !it.second }
                .collect { emit(it.first) }
            emit(progressMaxValue to progressMaxValue)
        }

    override val message: Flow<String>
        = flow {
            combine(mutableMessageFlow,end){
                    progress,done ->
                Pair(progress,done)
            }.takeWhile { !it.second }
            .collect { emit(it.first) }
            emit(mutableMessageFlow.value)
        }
}
